winsafe\gui\native_controls/
edit.rs

1use std::any::Any;
2use std::marker::PhantomPinned;
3use std::pin::Pin;
4use std::sync::Arc;
5
6use crate::co;
7use crate::decl::*;
8use crate::gui::{privs::*, *};
9use crate::macros::*;
10use crate::msg::*;
11use crate::prelude::*;
12
13struct EditObj {
14	base: BaseCtrl,
15	events: BaseCtrlEvents,
16	_pin: PhantomPinned,
17}
18
19native_ctrl! { Edit: EditObj => GuiEventsEdit;
20	/// Native
21	/// [edit](https://learn.microsoft.com/en-us/windows/win32/controls/about-edit-controls)
22	/// (text box) control.
23}
24
25impl Edit {
26	/// Instantiates a new `Edit` object, to be created on the parent window
27	/// with [`HWND::CreateWindowEx`](crate::HWND::CreateWindowEx).
28	///
29	/// # Panics
30	///
31	/// Panics if the parent window was already created – that is, you cannot
32	/// dynamically create an `Edit` in an event closure.
33	///
34	/// # Examples
35	///
36	/// ```no_run
37	/// use winsafe::{self as w, prelude::*, gui};
38	///
39	/// let wnd: gui::WindowMain; // initialized somewhere
40	/// # let wnd = gui::WindowMain::new(gui::WindowMainOpts::default());
41	///
42	/// let txt = gui::Edit::new(
43	///     &wnd,
44	///     gui::EditOpts {
45	///         position: gui::dpi(10, 10),
46	///         width: gui::dpi_x(120),
47	///         ..Default::default()
48	///     },
49	/// );
50	/// ```
51	#[must_use]
52	pub fn new(parent: &(impl GuiParent + 'static), opts: EditOpts) -> Self {
53		let ctrl_id = auto_id::set_if_zero(opts.ctrl_id);
54		let new_self = Self(Arc::pin(EditObj {
55			base: BaseCtrl::new(ctrl_id),
56			events: BaseCtrlEvents::new(parent, ctrl_id),
57			_pin: PhantomPinned,
58		}));
59
60		let self2 = new_self.clone();
61		let parent2 = parent.clone();
62		let text2 = opts.text.to_owned();
63		parent
64			.as_ref()
65			.before_on()
66			.wm(parent.as_ref().wnd_ty().creation_msg(), move |_| {
67				self2.0.base.create_window(
68					opts.window_ex_style,
69					"EDIT",
70					Some(&text2),
71					opts.window_style | opts.control_style.into(),
72					opts.position.into(),
73					SIZE::with(opts.width, opts.height),
74					&parent2,
75				);
76				ui_font::set(self2.hwnd());
77				parent2
78					.as_ref()
79					.add_to_layout(self2.hwnd(), opts.resize_behavior);
80				Ok(0) // ignored
81			});
82
83		new_self.default_message_handlers(parent);
84		new_self
85	}
86
87	/// Instantiates a new `Edit` object, to be loaded from a dialog resource
88	/// with [`HWND::GetDlgItem`](crate::HWND::GetDlgItem).
89	///
90	/// # Panics
91	///
92	/// Panics if the parent dialog was already created – that is, you cannot
93	/// dynamically create an `Edit` in an event closure.
94	#[must_use]
95	pub fn new_dlg(
96		parent: &(impl GuiParent + 'static),
97		ctrl_id: u16,
98		resize_behavior: (Horz, Vert),
99	) -> Self {
100		let new_self = Self(Arc::pin(EditObj {
101			base: BaseCtrl::new(ctrl_id),
102			events: BaseCtrlEvents::new(parent, ctrl_id),
103			_pin: PhantomPinned,
104		}));
105
106		let self2 = new_self.clone();
107		let parent2 = parent.clone();
108		parent.as_ref().before_on().wm_init_dialog(move |_| {
109			self2.0.base.assign_dlg(&parent2);
110			parent2
111				.as_ref()
112				.add_to_layout(self2.hwnd(), resize_behavior);
113			Ok(true) // ignored
114		});
115
116		new_self.default_message_handlers(parent);
117		new_self
118	}
119
120	fn default_message_handlers(&self, parent: &(impl GuiParent + 'static)) {
121		let self2 = self.clone();
122		let parent2 = parent.clone();
123		parent
124			.as_ref()
125			.before_on()
126			.wm_command(self.ctrl_id(), co::EN::CHANGE, move || {
127				// EN_CHANGE is first sent to the control before CreateWindowEx()
128				// returns, so if the user handles EN_CHANGE, the Edit HWND won't be
129				// set yet. So we set the HWND here.
130				if *self2.hwnd() == HWND::NULL {
131					let hctrl = parent2
132						.as_ref()
133						.hwnd()
134						.GetDlgItem(self2.ctrl_id())
135						.expect(DONTFAIL);
136					self2.0.base.set_hwnd(hctrl);
137				}
138				Ok(())
139			});
140	}
141
142	/// Hides any balloon tip by sending an
143	/// [`em::HideBalloonTip`](crate::msg::em::HideBalloonTip) message.
144	pub fn hide_balloon_tip(&self) -> SysResult<()> {
145		unsafe { self.hwnd().SendMessage(em::HideBalloonTip {}) }
146	}
147
148	/// Limits the number of characters that can be type by sending an
149	/// [`em::SetLimitText`](crate::msg::em::SetLimitText) message.
150	pub fn limit_text(&self, max_chars: Option<u32>) {
151		unsafe {
152			self.hwnd().SendMessage(em::SetLimitText { max_chars });
153		}
154	}
155
156	/// Replaces the currently selected text by sending an
157	/// [`em::ReplaceSel`](crate::msg::em::ReplaceSel) message.
158	pub fn replace_selection(&self, text: &str) {
159		let text16 = WString::from_str(text);
160		unsafe {
161			self.hwnd().SendMessage(em::ReplaceSel {
162				can_be_undone: true,
163				replacement_text: text16,
164			})
165		}
166	}
167
168	/// Sets the selection range of the text by sending an
169	/// [`em::SetSel`](crate::msg::em::SetSel) message.
170	///
171	/// # Examples
172	///
173	/// Selecting all text in the control:
174	///
175	/// ```no_run
176	/// use winsafe::{self as w, prelude::*, gui};
177	///
178	/// let my_edit: gui::Edit; // initialized somewhere
179	/// # let wnd = gui::WindowMain::new(gui::WindowMainOpts::default());
180	/// # let my_edit = gui::Edit::new(&wnd, gui::EditOpts::default());
181	///
182	/// my_edit.set_selection(0, -1);
183	/// ```
184	///
185	/// Clearing the selection:
186	///
187	/// ```no_run
188	/// use winsafe::gui;
189	///
190	/// let my_edit: gui::Edit; // initialized somewhere
191	/// # let wnd = gui::WindowMain::new(gui::WindowMainOpts::default());
192	/// # let my_edit = gui::Edit::new(&wnd, gui::EditOpts::default());
193	///
194	/// my_edit.set_selection(-1, -1);
195	/// ```
196	pub fn set_selection(&self, start: i32, end: i32) {
197		unsafe {
198			self.hwnd().SendMessage(em::SetSel { start, end });
199		}
200	}
201
202	/// Sets the text by calling
203	/// [`HWND::SetWindowText`](crate::HWND::SetWindowText).
204	pub fn set_text(&self, text: &str) -> SysResult<()> {
205		self.hwnd().SetWindowText(text)?;
206		Ok(())
207	}
208
209	/// Displays a balloon tip by sending an
210	/// [`em::ShowBalloonTip`](crate::msg::em::ShowBalloonTip) message.
211	pub fn show_ballon_tip(&self, title: &str, text: &str, icon: co::TTI) -> SysResult<()> {
212		let mut title16 = WString::from_str(title);
213		let mut text16 = WString::from_str(text);
214
215		let mut info = EDITBALLOONTIP::default();
216		info.set_pszTitle(Some(&mut title16));
217		info.set_pszText(Some(&mut text16));
218		info.ttiIcon = icon;
219
220		unsafe { self.hwnd().SendMessage(em::ShowBalloonTip { info: &info }) }
221	}
222
223	/// Retrieves the text by calling
224	/// [`HWND::GetWindowText`](crate::HWND::GetWindowText).
225	#[must_use]
226	pub fn text(&self) -> SysResult<String> {
227		self.hwnd().GetWindowText()
228	}
229}
230
231/// Options to create an [`Edit`](crate::gui::Edit) programmatically with
232/// [`Edit::new`](crate::gui::Edit::new).
233pub struct EditOpts<'a> {
234	/// Text of the control to be
235	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
236	///
237	/// Defaults to empty string.
238	pub text: &'a str,
239	/// Left and top position coordinates of control within parent's client
240	/// area, to be
241	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
242	///
243	/// Defaults to `gui::dpi(0, 0)`.
244	pub position: (i32, i32),
245	/// Control width to be
246	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
247	///
248	/// Defaults to `gui::dpi_x(100)`.
249	pub width: i32,
250	/// Control height to be
251	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
252	///
253	/// Defaults to `gui::dpi_y(23)`.
254	///
255	/// **Note:** You should change the default height only in a multi-line
256	/// edit, otherwise it will look off.
257	pub height: i32,
258	/// Edit styles to be
259	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
260	///
261	/// Defaults to `ES::AUTOHSCROLL | ES::NOHIDESEL`.
262	///
263	/// Suggestions:
264	/// * add `ES::PASSWORD` for a password input;
265	/// * add `ES::NUMBER` to accept only numbers;
266	/// * replace with `ES::MULTILINE | ES::WANTRETURN | ES::AUTOVSCROLL | ES::NOHIDESEL` for a multi-line edit.
267	pub control_style: co::ES,
268	/// Window styles to be
269	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
270	///
271	/// Defaults to `WS::CHILD | WS::GROUP | WS::TABSTOP | WS::VISIBLE`.
272	pub window_style: co::WS,
273	/// Extended window styles to be
274	/// [created](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw).
275	///
276	/// Defaults to `WS_EX::LEFT | WS_EX::CLIENTEDGE`.
277	pub window_ex_style: co::WS_EX,
278
279	/// The control ID.
280	///
281	/// Defaults to an auto-generated ID.
282	pub ctrl_id: u16,
283	/// Horizontal and vertical behavior of the control when the parent window
284	/// is resized.
285	///
286	/// **Note:** You should use `Vert::Resize` only in a multi-line edit.
287	///
288	/// Defaults to `(gui::Horz::None, gui::Vert::None)`.
289	pub resize_behavior: (Horz, Vert),
290}
291
292impl<'a> Default for EditOpts<'a> {
293	fn default() -> Self {
294		Self {
295			text: "",
296			position: dpi(0, 0),
297			width: dpi_x(100),
298			height: dpi_y(23),
299			control_style: co::ES::AUTOHSCROLL | co::ES::NOHIDESEL,
300			window_style: co::WS::CHILD | co::WS::GROUP | co::WS::TABSTOP | co::WS::VISIBLE,
301			window_ex_style: co::WS_EX::LEFT | co::WS_EX::CLIENTEDGE,
302			ctrl_id: 0,
303			resize_behavior: (Horz::None, Vert::None),
304		}
305	}
306}